export class URI {
  uricalss: UriEntry;
  constructor(input: string) {
    this.uricalss = new UriEntry(input);
    let errStr: string = this.uricalss.isFailed();
    if (errStr != "") {
      let err: BusinessError = new BusinessError(`Syntax Error. Invalid Uri string: The ${errStr}`);
      err.code = SyntaxErrorCodeId;
      throw err;
    }
  }
  static createFromParts(scheme: string, ssp: string, fragment: string): URI {
    let uriStr: string = scheme;
    uriStr += ':' + encodeURIComponent(ssp);
    if (fragment != null && fragment != "") {
      uriStr += '#' + encodeURIComponent(fragment);
    }
    return createNewUri(uriStr);
  }

  toString(): string {
    return toAscllString(this.uricalss.toString());
  }

  equals(other: URI): boolean {
    return this.uricalss.equals(other.uricalss);
  }

  equalsTo(other: URI): boolean {
    return this.uricalss.equals(other.uricalss);
  }
  checkIsAbsolute(): boolean {
    return this.uricalss.isAbsolute();
  }

  checkRelative(): boolean {
    return this.uricalss.isRelative();
  }

  checkOpaque(): boolean {
    return this.uricalss.isOpaque();
  }

  checkHierarchical(): boolean {
    return this.uricalss.isHierarchical();
  }
  addQueryValue(key: string, value: string): URI {
    let uriStr = this.uricalss.addQueryValue(encodeURIComponent(key), encodeURIComponent(value));
    return createNewUri(uriStr);
  }
  addEncodedSegment(pathSegment: string): URI {
    let uriStr = this.uricalss.addSegment(pathSegment);
    return createNewUri(uriStr);
  }

  addSegment(pathSegment: string): URI {
    let uriStr = this.uricalss.addSegment(encodeURIComponent(pathSegment));
    return createNewUri(uriStr);
  }

  getQueryValue(key: string): string | null {
    if (this.query == null) {
      return null;
    }
    let queryStrs: string[] = this.query!.split('&');
    for (let item of queryStrs) {
      if (key == "" && item == "") {
        return "";
      }
      let str = item.split('=');
      if (str.length == 1 && this.decodeSafelyInner(str[0]) == key) {
        return "";
      } else if (this.decodeSafelyInner(str[0]) == key) {
        return this.decodeSafelyInner(item.substring(str[0].length + 1).replace(new RegExp("\\+", "g"), ' '));
      }
    }
    return null;
  }

  decodeSafelyOut(input: string): string {
    let decodedString: string = "";
    let decodedTemp: string = "";
    let index: number = 0;
    while (index < input.length) {
      if (input[index] == '%' && new RegExp("[0-9A-Fa-f]{2}").test(input.slice(index + 1, index + 3))) {
        const encodedChar = input.slice(index, index + 3);
        try {
          decodedString += decodeURIComponent(decodedTemp + encodedChar);
          decodedTemp = "";
        } catch (e) {
          decodedTemp += encodedChar;
        }
        index += 3;
        continue;
      }
      if (decodedTemp == "") {
        decodedString += input[index];
      } else {
        decodedString += decodedTemp;
        decodedString += input[index];
        decodedTemp = "";
      }
      index++;
    }
    return decodedTemp == "" ? decodedString : decodedString += decodedTemp;
  }

  decodeSafelyInner(input: string): string {
    if (input == "") {
      return input;
    }
    let strVal: string = "";
    try {
      strVal = decodeURIComponent(input);
    } catch (e) {
      strVal = this.decodeSafelyOut(input);
    }
    return strVal;
  }

  getQueryNames(): Array<string> {
    let names: Set<string> = new Set<string>();
    let query = this.query;
    if (this.query == null) {
      return new Array<string>();
    }
    let start: number = 0;
    while (start < query!.length) {
      let next: number = query!.indexOf('&', start);
      let end: number = (next == -1) ? query!.length : next;
      let separator: number = query!.indexOf('=', start);
      if (separator > end || separator == -1) {
        separator = end;
      }
      let name: string = query!.substring(start, separator);
      names.add(this.decodeSafelyInner(name));
      start = end + 1;
    }
    return Array.from<string>(names);
  }

  getQueryValues(key: string): Array<string> {
    let values = new Array<string>();
    if (this.query == null) {
      return values;
    }
    let queryStrs: string[] = this.query!.split('&');
    for (let item of queryStrs) {
      if (key == "" && item == "") {
        values.push(item);
      }
      let str: string[] = item.split('=');
      if (str.length == 1 && this.decodeSafelyInner(str[0]) == key) {
        values.push("");
      } else if (this.decodeSafelyInner(str[0]) == key) {
        values.push(this.decodeSafelyInner(item.substring(str[0].length + 1)));
      }
    }
    return values;
  }

  getBooleanQueryValue(key: string, defaultValue: boolean): boolean {
    let flag = this.getQueryValue(key);
    if (flag == null) {
      return defaultValue;
    }
    flag = flag.toLocaleLowerCase();
    return 'false' != flag && '0' != flag;
  }

  getLastSegment(): string {
    let segments = this.uricalss.getSegment();
    if (segments.length == 0) {
      return "";
    }
    return this.decodeSafelyInner(segments[segments.length - 1]);
  }

  getSegment(): Array<string> {
    let array = new Array<string>();
    let segments = this.uricalss.getSegment();
    if (segments.length > 0) {
      segments.forEach(
        (element: string, i: number, arr: Array<string>) => {
          array.push(this.decodeSafelyInner(element));
        });
    }
    return array;
  }

  clearQuery(): URI {
    let uriStr: string = this.uricalss.clearQuery();
    return createNewUri(uriStr);
  }

  normalize(): URI {
    let uriStr: string = this.uricalss.normalize();
    return createNewUri(uriStr);
  }

  get scheme(): string | null {
    return this.uricalss.getScheme();
  }

  get authority(): string | null {
    if (this.uricalss.getAuthority() == null) {
      return null;
    }
    let thisAuthority: string = this.uricalss.getAuthority();
    if (thisAuthority.indexOf('[') != -1) {
      let arr: string[] = thisAuthority.split('[');
      let brr: string[] = arr[1].split(']');
      arr[1] = '[' + brr[0] + ']';
      arr[2] = brr[1];
      arr[0] = this.decodeSafelyInner(arr[0]);
      arr[2] = this.decodeSafelyInner(arr[2]);
      return join(arr, "");
    } else {
      return this.decodeSafelyInner(thisAuthority);
    }
  }

  get ssp(): string {
    let thisSsp: string = this.uricalss.getSsp();
    if (thisSsp.indexOf('[') != -1) {
      let arr: string[] = thisSsp.split('[');
      let brr: string[] = arr[1].split(']');
      arr[1] = '[' + brr[0] + ']';
      arr[2] = brr[1];
      arr[0] = this.decodeSafelyInner(arr[0]);
      arr[2] = this.decodeSafelyInner(arr[2]);
      return join(arr, "");
    } else {
      return this.decodeSafelyInner(thisSsp);
    }
  }

  get userInfo(): string | null {
    return this.uricalss.getUserinfo() == "" ? null : this.decodeSafelyInner(this.uricalss.getUserinfo());
  }

  get host(): string | null {
    return this.uricalss.getHost() == "" ? null : this.uricalss.getHost();
  }

  get port(): string {
    return String(this.uricalss.getPort());
  }

  get path(): string | null {
    return this.uricalss.getPath() == "" ? null : this.decodeSafelyInner(this.uricalss.getPath());
  }

  get query(): string | null {
    return this.uricalss.getQuery() == "" ? null : this.decodeSafelyInner(this.uricalss.getQuery());
  }

  get fragment(): string | null {
    return this.uricalss.getFragment() == "" ? null : this.decodeSafelyInner(this.uricalss.getFragment());
  }

  get encodedUserInfo(): string | null {
    return this.uricalss.getUserinfo() == "" ? null : this.uricalss.getUserinfo();
  }

  get encodedPath(): string | null {
    return this.uricalss.getPath() == "" ? null : this.uricalss.getPath();
  }

  get encodedQuery(): string | null {
    return this.uricalss.getQuery() == "" ? null : this.uricalss.getQuery();
  }

  get encodedFragment(): string | null {
    return this.uricalss.getFragment() == "" ? null : this.uricalss.getFragment();
  }

  get encodedAuthority(): string | null {
    if (this.uricalss.getAuthority() == "") {
      return null;
    }
    let thisAuthority: string = this.uricalss.getAuthority();
    if (thisAuthority.indexOf('[') != -1) {
      let arr: string[] = thisAuthority.split('[');
      let brr: string[] = arr[1].split(']');
      arr[1] = '[' + brr[0] + ']';
      arr[2] = brr[1];
      return join(arr, "");
    } else {
      return thisAuthority;
    }
  }

  get encodedSSP(): string {
    let thisSsp: string = this.uricalss.getSsp();
    if (thisSsp.indexOf('[') != -1) {
      let arr: string[] = thisSsp.split('[');
      let brr: string[] = arr[1].split(']');
      arr[1] = '[' + brr[0] + ']';
      arr[2] = brr[1];
      return join(arr, "");
    } else {
      return thisSsp;
    }
  }
}

const TypeErrorCodeId = 401;
const SyntaxErrorCodeId = 10200002;
class BusinessError extends Error {
  code: number;
  constructor(msg: string) {
    super(msg);
    this.name = 'BusinessError';
    this.code = TypeErrorCodeId;
  }
}


function toAscllString(uriStr: string): string {
  return encodeURI(uriStr)
    .replace(new RegExp("%5B", 'g'), '[')
    .replace(new RegExp("%5D", 'g'), ']')
    .replace(new RegExp("%25", 'g'), '%');
}
function createNewUri(uriStr: string): URI {
  return new URI(uriStr);
}
function join(arr: string[], s: string): string {
  let result = '';
  for (let i = 0; i < arr.length; i++) {
    result += arr[i];
    if (i != arr.length - 1) {
      result += s;
    }
  }
  return result;
}

const MAX_BIT_SIZE: number = 128;
class UriData {
  port: number = -1;
  scheme: string = "";
  userInfo: string = "";
  host: string = "";
  query: string = "";
  fragment: string = "";
  path: string = "";
  authority: string = "";
  SchemeSpecificPart: string = "";
};

export class UriEntry {

  uriData: UriData = new UriData();
  data: string = "";
  inputUri: string = "";
  errStr: string = "";

  constructor(input: string) {
    this.errStr = "";
    if (input == "") {
      this.errStr = "uri is empty";
      return;
    }
    this.inputUri = input;
    this.analysisUri();
  }
  assignSchemeSpecificPart() {
    this.uriData.SchemeSpecificPart += this.data;
    if (this.uriData.query != "") {
      this.uriData.SchemeSpecificPart += "?";
      this.uriData.SchemeSpecificPart += this.uriData.query;
    }
  }
  analysisUri() {
    this.data = this.inputUri;
    let pos = this.data.indexOf('#');// Fragment
    if (pos != -1) {
      this.analysisFragment(pos);
      if (this.errStr != "") {
        return;
      }
    }
    pos = this.data.indexOf('?');// Query
    if (pos != -1) {
      this.analysisQuery(pos);
      if (this.errStr != "") {
        return;
      }
    }
    pos = this.data.indexOf(':'); // Scheme
    if (pos != -1) {
      this.analysisScheme(pos);
      if (this.errStr != "") {
        return;
      }
    } else {
      this.specialPath();
      if (this.errStr != "") {
        return;
      }
      this.assignSchemeSpecificPart();
      return;
    }
    pos = this.data.indexOf("//"); // userInfo path host port ipv4 or ipv6
    if (pos == 0) {
      this.assignSchemeSpecificPart();
      this.data = this.data.substring(2); // 2:Intercept the string from the second subscript
      this.analysisHostAndPath();
      if (this.errStr != "") {
        return;
      }
    } else if (this.data[0] == '/') {
      this.uriData.path = this.data;
      this.assignSchemeSpecificPart();
      this.data = "";
    } else {
      this.assignSchemeSpecificPart();
      this.uriData.query = "";
      this.data = "";
    }
  }

  checkCharacter(data: string, rule: Array<boolean>, flag: boolean): boolean {
    let dataLen = data.length;
    for (let i = 0; i < dataLen; i++) {
      let charCode = data.charCodeAt(i);
      if (charCode >= 0 && charCode < 128) { // 128:ASCII Max Number
        if (!rule[charCode]) {
          return false;
        }
      } else if (!flag) {
        return false;
      }
    }
    return true;
  }

  specialPath() {
    if (!this.checkCharacter(this.data, UriRule.rulePath, true)) {
      this.errStr = "SpecialPath does not conform to the rule";
      return;
    }
    this.uriData.path = this.data;
    this.data = "";
  }

  analysisFragment(pos: number) {
    if (pos == 0) {
      this.errStr = "#It can't be the first";
      return;
    }
    let fragment = this.data.substring(pos + 1);
    if (!this.checkCharacter(fragment, UriRule.ruleUrlc, true)) {
      this.errStr = "Fragment does not conform to the rule";
      return;
    }
    this.uriData.fragment = fragment;
    this.data = this.data.substring(0, pos);
  }
  analysisQuery(pos: number) {
    let query = this.data.substring(pos + 1);
    if (!this.checkCharacter(query, UriRule.ruleUrlc, true)) {
      this.errStr = "Query does not conform to the rule";
      return;
    }
    this.uriData.query = query;
    this.data = this.data.substring(0, pos);
  }

  analysisScheme(pos: number) {
    let slashPos = this.data.indexOf('/');
    if (slashPos != -1 && slashPos < pos) {
      this.specialPath();
      this.uriData.SchemeSpecificPart += (this.uriData.path);
      this.uriData.SchemeSpecificPart += ("?");
      this.uriData.SchemeSpecificPart += (this.uriData.query);
      this.data = "";
    } else {
      let code = this.data.charCodeAt(0);
      if (code >= 0 && code < 128 &&
        !UriRule.ruleAlpha[code]) {
        this.errStr = "Scheme the first character must be a letter";
        return;
      }
      let scheme = this.data.substring(0, pos);
      if (!this.checkCharacter(scheme, UriRule.ruleScheme, false)) {
        this.errStr = "scheme does not conform to the rule";
        return;
      }
      this.uriData.scheme = scheme;
      this.data = this.data.substring(pos + 1);

    }
  }
  analysisHost(isLawfulProt: boolean) {
    // find ipv4 or ipv6 or host
    if (this.data[0] == '[') {
      if (this.data[this.data.length - 1] == ']') {
        // IPV6
        if (!isLawfulProt) {
          this.errStr = "Prot does not conform to the rule";
          return;
        }
        this.analysisIPV6();
      } else {
        this.errStr = "IPv6 is missing a closing bracket";
        return;
      }
    } else {
      if (this.data.indexOf('[') != -1 || this.data.indexOf(']') != -1) {
        this.errStr = "host does not conform to the rule";
        return;
      }
      // ipv4
      if (!isLawfulProt || !this.analysisIPV4()) {
        this.uriData.port = -1;
        this.uriData.host = "";
        this.uriData.userInfo = "";
      }
    }
  }
  analysisHostAndPath() {
    if (this.data == "") {
      return;
    }
    // find path
    let pos = this.data.indexOf('/');
    if (pos != -1) {
      this.analysisPath(pos);
      if (this.errStr != "") {
        return;
      }
    }
    this.uriData.authority = this.data;
    // find UserInfo
    pos = this.data.indexOf('@');
    if (pos != -1) {
      this.analysisUserInfo(pos);
      if (this.errStr != "") {
        return;
      }
    }
    let isLawfulProt = true;
    // find port
    pos = this.data.lastIndexOf(':');
    if (pos != -1) {
      let pos1 = this.data.lastIndexOf(']');
      if (pos1 == -1 || pos > pos1) {
        isLawfulProt = this.analysisPort(pos);
      }
      if (this.errStr != "") {
        return;
      }
    }
    this.analysisHost(isLawfulProt);
  }
  analysisPath(pos: number) {
    let path = this.data.substring(pos);
    if (!this.checkCharacter(path, UriRule.rulePath, true)) {
      this.errStr = "path does not conform to the rule";
      return;
    }
    this.uriData.path = path;
    this.data = this.data.substring(0, pos);
  }
  analysisUserInfo(pos: number) {
    let userInfo = this.data.substring(0, pos);
    if (!this.checkCharacter(userInfo, UriRule.ruleUserInfo, true)) {
      this.errStr = "userInfo does not conform to the rule";
      return;
    }
    this.uriData.userInfo = userInfo;
    this.data = this.data.substring(pos + 1);
  }
  analysisPort(pos: number) {
    let port = this.data.substring(pos + 1);
    if (!this.checkCharacter(port, UriRule.rulePort, true)) {
      this.errStr = "port does not conform to the rule";
      return false;
    } else if (this.checkCharacter(port, UriRule.ruleDigit, false)) {
      if (port.length == 0 || port.length > 10) { // 10:The maximum number of bits for int value
        return false;
      }
      let tempPort: number = Number(port);
      //INT32_MAX   2147483647 
      if (tempPort < 0 || tempPort > 2147483647) {
        return false;
      }
      this.uriData.port = tempPort;
      this.data = this.data.substring(0, pos);
      return true;
    } else {
      this.data = this.data.substring(0, pos);
      return false;
    }
  }
  analysisIPV4() {
    /**
     * 正则图解析 https://www.jyshare.com/front-end/7625
       regex_match默认使用的是完全匹配模式    js需要要加上^$
    */
    let ipv4 = new RegExp("^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)$");
    let hostname = new RegExp("^(([a-zA-Z0-9]([a-zA-Z0-9\\-~_|\\+{}!$&=,;:'()\\*\\s]*[a-zA-Z0-9])?\\.)+([a-zA-Z]"
      +"([a-zA-Z0-9\\-~_|\\+{}!$&=,;:'()\\*\\s]*[a-zA-Z0-9\\-~_|\\+{}!$&=,;:'()\\*\\s])?))$|^([a-zA-Z0-9]([a-zA-Z0-9\\-~_|\\+{}!$&=,;:'()\\*\\s]*[a-zA-Z0-9])?)$");
    let isIpv4 = ipv4.test(this.data);
    let isHosName = hostname.test(this.data);
    if (!isIpv4 && !isHosName) {
      return false;
    } else {
      this.uriData.host = this.data;
      this.data = "";
      return true;
    }
  }
  analysisIPV6() {
    let str = this.data.substr(1, this.data.length - 2); // 2:Intercept the string from the second subscript
    let ipv6 = new RegExp("^(::|(:((:[0-9A-Fa-f]{1,4}){1,7}))|(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|"
      + "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(([0-9A-Fa-f]{1,4}:){2}"
      + "(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})"
      + "|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){5}"
      + "(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|"
      + "(((:(:[0-9A-Fa-f]{1,4}){0,5}:)|(([0-9A-Fa-f]{1,4}:){1}(:[0-9A-Fa-f]{1,4}){0,4}:)"
      + "|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}:)|(([0-9A-Fa-f]{1,4}:){3}"
      + "(:[0-9A-Fa-f]{1,4}){0,2}:)|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4})?:)|"
      + "(([0-9A-Fa-f]{1,4}:){5}:)|(([0-9A-Fa-f]{1,4}:){6}))((25[0-5]|2[0-4]\\d|1\\d{2}|"
      + "[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)))(%[a-zA-Z0-9._]+)?$");
    if (!ipv6.test(str)) {
      this.errStr = "ipv6 does not conform to the rule";
      return;
    }
    this.uriData.host = this.data;
    this.data = "";
  }
  equals(other: UriEntry): boolean {
    if (this.uriData.port != other.uriData.port) {
      return false;
    }
    if (this.uriData.scheme != other.uriData.scheme) {
      return false;
    }
    if (this.uriData.userInfo != other.uriData.userInfo) {
      return false;
    }
    if (this.uriData.host != other.uriData.host) {
      return false;
    }
    if (this.uriData.query != other.uriData.query) {
      return false;
    }
    if (this.uriData.fragment != other.uriData.fragment) {
      return false;
    }
    if (this.uriData.path != other.uriData.path) {
      return false;
    }
    if (this.uriData.authority != other.uriData.authority) {
      return false;
    }
    if (this.uriData.SchemeSpecificPart != other.uriData.SchemeSpecificPart) {
      return false;
    }
    return true;
  }

  toString(): string {
    return this.inputUri;
  }

  isAbsolute(): boolean {
    return this.uriData.scheme != "";
  }

  isRelative(): boolean {
    return this.uriData.scheme == "";
  }

  isOpaque(): boolean {
    return !this.isHierarchical();
  }
  isHierarchical(): boolean {
    let index = this.inputUri.indexOf(':');
    if (index == -1) {
      return true;
    }
    if (this.inputUri.length == index + 1) {
      return false;
    }
    return this.inputUri[index + 1] == '/';
  }
  addQueryValue(key: string, value: string) {
    return this.buildUriString("query", key + "=" + value);
  }
  addSegment(pathSegment: string) {
    return this.buildUriString("segment", pathSegment);
  }
  buildUriString(str: string, param: string) {
    let result = "";
    if (this.uriData.scheme != "") {
      result += this.uriData.scheme + ":";
    }
    if (this.uriData.authority != "") {
      result += "//" + this.uriData.authority;
    }
    if (this.uriData.path != "") {
      result += this.uriData.path;
    }
    if (str == "segment") {
      let lastChar = result[result.length - 1];
      if (lastChar == '/') {
        result += param;
      } else {
        result += "/" + param;
      }
    }
    if (str != "clearquery") {
      if (this.uriData.query == "") {
        if (str == "query") {
          result += "?" + param;
        }
      } else {
        result += "?" + this.uriData.query;
        if (str == "query") {
          result += "&" + param;
        }
      }
    }
    if (this.uriData.fragment != "") {
      result += "#" + this.uriData.fragment;
    }
    return result;
  }
  getSegment(): Array<string> {
    let segments = new Array<string>();
    if (this.uriData.path == "") {
      return segments;
    }
    let previous = 0;
    let current = 0;
    for (current = this.uriData.path.indexOf('/', previous); current != -1;
      current = this.uriData.path.indexOf('/', previous)) {
      if (previous < current) {
        let segment = this.uriData.path.substring(previous, current);
        segments.push(segment);
      }
      previous = current + 1;
    }
    if (previous < this.uriData.path.length) {
      segments.push(this.uriData.path.substring(previous));
    }
    return segments;
  }

  isFailed(): string {
    return this.errStr;
  }

  normalize(): string {
    let temp = new Array<string>;
    let pathLen = this.uriData.path.length;
    if (pathLen == 0) {
      return this.inputUri;
    }
    let pos = 0;
    let left = 0;
    while ((pos = this.uriData.path.indexOf('/', left)) != -1) {
      temp.push(this.uriData.path.substring(left, pos));
      left = pos + 1;
    }
    if (left != pathLen) {
      temp.push(this.uriData.path.substring(left));
    }
    const STR_DOTDOT = '..';
    let tempLen = temp.length;
    let normalizeTemp = new Array<string>;
    for (let i = 0; i < tempLen; ++i) {
      if (temp[i] != "" && temp[i] != '.' && temp[i] != STR_DOTDOT) {
        normalizeTemp.push(temp[i]);
      }
      if (temp[i] == STR_DOTDOT) {
        if (normalizeTemp.length != 0 && normalizeTemp[normalizeTemp.length - 1] != STR_DOTDOT) {
          normalizeTemp.pop();
        } else {
          normalizeTemp.push(temp[i]);
        }
      }
    }
    let normalizePath = "";
    tempLen = normalizeTemp.length;
    if (tempLen == 0) {
      normalizePath = "/";
    } else {
      for (let i = 0; i < tempLen; ++i) {
        normalizePath += "/" + normalizeTemp[i];
      }
    }
    return this.split(normalizePath);
  }

  split(path: string): string {
    let normalizeUri = "";
    if (this.uriData.scheme != "") {
      normalizeUri += this.uriData.scheme + ":";
    }
    if (this.uriData.path == "") {
      normalizeUri += this.uriData.SchemeSpecificPart;
    } else {
      if (this.uriData.host != "") {
        normalizeUri += "//";
        if (this.uriData.userInfo != "") {
          normalizeUri += this.uriData.userInfo + "@";
        }
        normalizeUri += this.uriData.host;
        if (this.uriData.port != -1) {
          normalizeUri += ":" + String(this.uriData.port);
        }
      } else if (this.uriData.authority != "") {
        normalizeUri += "//" + this.uriData.authority;
      }
      normalizeUri += path;
    }
    if (this.uriData.query != "") {
      normalizeUri += "?" + this.uriData.query;
    }
    if (this.uriData.fragment != "") {
      normalizeUri += '#' + this.uriData.fragment;
    }
    return normalizeUri;
  }

  getScheme() {
    return this.uriData.scheme;
  }

  getAuthority() {
    return this.uriData.authority;
  }

  getSsp() {
    return this.uriData.SchemeSpecificPart;
  }

  getUserinfo() {
    return this.uriData.userInfo;
  }

  getHost() {
    return this.uriData.host;
  }

  getPort() {
    return this.uriData.port;
  }

  getPath() {
    return this.uriData.path;
  }

  getQuery() {
    return this.uriData.query;
  }

  getFragment() {
    return this.uriData.fragment;
  }

  clearQuery() {
    return this.buildUriString("clearquery", "");
  }
}

class UriRule {
  public static ruleAlpha = new Array<boolean>(128);
  public static ruleScheme = new Array<boolean>(128);
  public static ruleUrlc = new Array<boolean>(128);
  public static rulePath = new Array<boolean>(128);
  public static ruleUserInfo = new Array<boolean>(128);
  public static ruleDigit = new Array<boolean>(128);
  public static rulePort = new Array<boolean>(128);
  static {
    let digitAggregate = "0123456789";
    UriRule.setBit(UriRule.ruleDigit, digitAggregate);

    let alphasAggregate = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    UriRule.setBit(UriRule.ruleAlpha, alphasAggregate);

    let schemeAggregate = digitAggregate + alphasAggregate + "+-.| _-~!$&=,;'(){}*";
    UriRule.setBit(UriRule.ruleScheme, schemeAggregate);

    let uricAggregate = schemeAggregate + ";/?:@&=$,[]_!~*'()%\"";
    UriRule.setBit(UriRule.ruleUrlc, uricAggregate);

    let pathAggregate = schemeAggregate + ";/:@&=$,_!~*'()%";
    UriRule.setBit(UriRule.rulePath, pathAggregate);

    let userInfoAggregate = schemeAggregate + ";:&=$,_!~*'()%";
    UriRule.setBit(UriRule.ruleUserInfo, userInfoAggregate);

    let portAggregate = digitAggregate + alphasAggregate + ".:@-;&=+$,-_!~*'()";
    UriRule.setBit(UriRule.rulePort, portAggregate);
  }
  static setBit(arr: Array<boolean>, str: string) {
    for (let i = 0; i < arr.length; i++) {
      arr[i] = false;
    }
    for (let i = 0; i < str.length; i++) {
      let code = str.charCodeAt(i);
      arr[code] = true;
    }
  }
}